﻿using System;
using System.Collections.Concurrent;
using System.Linq;
using CS.Diagnostics;

namespace CS.Caching
{
    /// <summary>
    ///   FuncCache 针对缓存值方法调用的缓存
    /// <remarks>
    /// 过期主动更新数据
    /// </remarks>
    /// </summary>
    /// 
    /// <description class = "CS.Caching.FuncCache">
    ///   1. 被动模式下会先耗光缓存设定的数量再进行移去过期检查。该模式下以创建时间为过期判断时间点。
    ///   2. 主动模式下每次获取缓存内容时都全面检查。该模式下以最后访问时间为过期判断时间点。
    ///   <para>Note:主动模式按缓存创建时间过期，被动模式按缓存访问时间过期（经常使用的缓存，很难过期）。</para>
    /// </description>
    /// 
    /// <history>
    ///   2010-3-22 14:23:37 , zhouyu ,  创建	
    ///   2010-6-24 zhouyu 加入缓存依赖项，可以依赖一个具体的文件，缺点，文件里有关联时并不能依赖关联。   
    ///   2013-9-22 zhouyu 移除System.Web 空间依赖，将Dependency改为Funtion来执行获取缓存项
    ///   2013-9-24 zhouyu 修改原Dictionary为ConcurrentDictionary
    ///  </history>
    public class FuncCache<TKey, TValue> where TValue : class
    {

        #region 属性

        /// <summary>
        /// 缓存生存时间(秒)
        /// </summary>
        public int LifeTime { get; set; }
        /// <summary>
        /// 最大缓存数量，超过自动删除使用次数较少的对象
        /// </summary>
        public int MaxCache { get; set; }
        /// <summary>
        /// 缓存名字
        /// </summary>
        public string CacheName { get; set; }
        ///// <summary>
        ///// 本地缓存名字
        ///// </summary>
        //public string LocalCacheName { get; set; }
        /// <summary>
        /// 缓存数量
        /// </summary>
        public int Caches
        {
            get { return CacheDic.Count; }
        }
        /// <summary>
        /// 缓存过期模式
        /// </summary>
        public CacheExpireMode ExpireMode { get; set; }
        /// <summary>
        /// 缓存项
        /// </summary>
        protected ConcurrentDictionary<TKey, CacheBag<TKey, TValue>> CacheDic;

        #endregion


        /// <summary>
        /// 默认构造
        /// Note:Passive下如果缓存在有效期内一直使用则永不过期
        /// </summary>
        internal FuncCache()
            : this(10, 128, CacheExpireMode.ByAccessTime)
        {
        }

        /// <summary>
        /// 初始化构造
        /// </summary>
        /// <param name="lifeTime">生命周期，秒</param>
        /// <param name="maxCache">最大缓存数量</param>
        /// <param name="expireMode">过期模式</param>

        internal FuncCache(int lifeTime, int maxCache, CacheExpireMode expireMode)
        {
            LifeTime = lifeTime;
            CacheName = "FuncCache_";
            MaxCache = maxCache;
            CacheDic = new ConcurrentDictionary<TKey, CacheBag<TKey, TValue>>();
            ExpireMode = expireMode;
        }

        /// <summary>
        /// 通过缓存索引获取值
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public virtual TValue this[TKey key]
        {
            get { return Get(key); }
            //set { Add(key, value); }
        }

        /// <summary>
        /// 获取缓存中的值
        /// </summary>
        /// <param name="key">缓存的键</param>
        /// <returns>未找到缓存时为default(T)，否则返回正确的结果</returns>
        public virtual TValue Get(TKey key)
        {
            TValue rst;
            TryGet(key, out rst);
            return rst;
        }

        /// <summary>
        /// 获取缓存内容
        /// </summary>
        /// <param name="key">键</param>
        /// <param name="value">试图获取的值</param>
        /// <returns>是否获取成功</returns>
        public virtual bool TryGet(TKey key, out TValue value)
        {
            if (CacheDic.ContainsKey(key))
            {
                var bag = CacheDic[key];
                if (bag == null) //移除无效数据
                {
                    Remove(key);
                    value = default(TValue);
                    return false;
                }
                value = bag.Value;
                bag.AccessTime = DateTime.Now;
                bag.Hits++;
                return true;
            }
            //不存在缓存的返回
            value = default(TValue);
            return false;
        }

        /// <summary>
        /// 把对象按指定的键值保存到缓存中
        /// </summary>
        /// <param name="key">键</param>
        /// <param name="value">保存的对象</param>
        /// <param name="function">缓存的依赖项</param>
        public virtual void Add(TKey key, TValue value, Func<TValue> function)
        {
            if (function == null) throw new ArgumentException("Invoke function delegate is indispensable.", "function");
            SetCache(key, value, function);
        }

        /// <summary>
        /// 把对象按指定的键值保存到缓存中
        /// </summary>
        /// <param name="key">键值</param>
        /// <param name="value">保存的对象</param>
        /// <param name="function">缓存的依赖项</param>
        protected virtual void SetCache(TKey key, TValue value, Func<TValue> function)
        {
            CacheDic[key] = new CacheBag<TKey, TValue>(key, value, function);
        }

        /// <summary>
        /// 主动模式时(且缓存数量不超过最大值) 按访问时间过期移除缓存
        /// </summary>
        protected virtual void RemoveExpireCaches()
        {
            var now = DateTime.Now;
            if (ExpireMode == CacheExpireMode.ByAccessTime)
            {
                foreach (var cache in CacheDic.Values.Where(cache => cache.AccessTime.AddSeconds(LifeTime) <= now))
                {
                    cache.Reload();
                    Tracer.Debug("{0} has been reload.", cache.Key);
                }
            }
            if (ExpireMode == CacheExpireMode.ByCreateTime)
            {
                foreach (var cache in CacheDic.Values.Where(cache => cache.CreateTime.AddSeconds(LifeTime) <= now))
                {
                    cache.Reload();
                    Tracer.Debug("{0} has been reload.", cache.Key);
                }
            }
        }

        /// <summary>
        /// 超过设定缓存最大数量时 不论时间是否过期移去很少用到的Cache
        /// </summary>
        protected virtual void RemoveMinHitCache()
        {
            if (Caches < MaxCache) return;
            //查找很少用到的Cache并移除
            var q = CacheDic.Values.OrderBy(x => x.Hits).FirstOrDefault();
            if (q != null) Remove(q.Key);
        }

        /// <summary>
        /// 清理过期相关的工作，将在单独线程中工作
        /// </summary>
        public void ClearWorker()
        {
            RemoveMinHitCache();
            RemoveExpireCaches();
            Tracer.Debug("{0} has been cleared.", CacheName);
        }

        /// <summary>
        /// 在缓存中删除指定键值的对象
        /// </summary>
        /// <param name="key">键值</param>
        public virtual void Remove(TKey key)
        {
            CacheBag<TKey, TValue> bag;
            CacheDic.TryRemove(key, out bag);
            Tracer.Debug("Key={0} has been removed.", key);
        }

        /// <summary>
        /// 清除缓存中所有对象
        /// </summary>
        public virtual void Clear()
        {
            CacheDic.Clear();
            Tracer.Debug("All caches  has been removed.");
        }

        #region 缓存包装器

        /// <summary>
        /// 缓存内容的再包装
        /// </summary>
        /// <typeparam name="TK">缓存内容的键</typeparam>
        /// <typeparam name="TV">缓存内容</typeparam>
        [Serializable]
        protected class CacheBag<TK, TV> where TV : class
        {
            /// <summary>
            /// 缓存内容的键
            /// </summary>
            public TK Key { get; set; }

            private TV _value;
            /// <summary>
            /// 缓存内容
            /// </summary>
            public TV Value
            {
                get { return _value ?? (_value = Function.Invoke()); }
                set { _value = value; }
            }
            /// <summary>
            /// 创建时间
            /// </summary>
            public DateTime CreateTime { get; set; }
            /// <summary>
            /// 访问的时间
            /// </summary>
            public DateTime AccessTime { get; set; }
            /// <summary>
            /// 命中次数，命中次数少的使用得少，优先移去缓存
            /// </summary>
            public int Hits { get; set; }
            /// <summary>
            /// 获取缓存的委托方法
            /// </summary>
            public Func<TV> Function { get; set; }
            /// <summary>
            /// 重载数据
            /// </summary>
            public void Reload()
            {
                Value = Function.Invoke();
            }

            internal CacheBag(TK key, TV value, Func<TV> function)
            {
                Hits = 0;
                Key = key;
                Value = value;
                Function = function;
                AccessTime = DateTime.Now;
                CreateTime = DateTime.Now;
            }

        }

        #endregion

    }

    /// <summary>
    /// 缓存释放与过期方式枚举
    /// </summary>
    public enum CacheExpireMode
    {
        /// <summary>
        /// 按访问时间过期释放
        /// </summary>
        ByAccessTime,

        /// <summary>
        /// 按创建时间过期释放
        /// </summary>
        ByCreateTime,
    }
}